package chapter23;

import chapter22.SimpleInject;
import net.sf.cglib.proxy.Enhancer;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author lzy
 * @version v1.0
 * Create DateTime: 2019/2/27 16:26
 */
public class CGLibContainer {
    static Class<?>[] aspects = {
            ServiceLogAspect.class, ExceptionAspect.class};
    static Map<Class<?>, Map<InterceptorPoint, List<Method>>> interceptMethodMap = new HashMap<>();
    private static Map<Class, Object> map = new ConcurrentHashMap<>();

    static {
        init();
    }

    public static <T> T getInstance(Class<T> cls) {
        try {
            T obj = createInstance(cls);
            Field[] fields = cls.getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(SimpleInject.class)) {
                    if (!field.isAccessible()) {
                        field.setAccessible(true);
                    }
                    Class<?> typeClass = field.getType();
                    field.set(obj, getInstance(typeClass));
                }
            }
            return obj;
        } catch (IllegalAccessException | InstantiationException e) {
            throw new RuntimeException(e);
        }
    }

    public static List<Method> getInterceptMethods(Class<?> cls, InterceptorPoint point) {
        Map<InterceptorPoint, List<Method>> map = interceptMethodMap.get(cls);
        if (map == null) {
            return Collections.emptyList();
        }
        List<Method> methods = map.get(point);
        if (methods == null) {
            return Collections.emptyList();
        }
        return methods;
    }

    private static <T> T createInstance(Class<T> cls) throws IllegalAccessException, InstantiationException {
        if (!interceptMethodMap.containsKey(cls)) {
            return cls.newInstance();
        }
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(cls);
        enhancer.setCallback(new AspectInterceptor());
        return (T) enhancer.create();
    }

    private static void init() {
        for (Class<?> cls : aspects) {
            Aspect aspect = cls.getAnnotation(Aspect.class);
            if (cls != null) {
                Method before = getMethod(cls, "before", Object.class, Method.class, Object[].class);
                Method after = getMethod(cls, "after", Object.class, Method.class, Object[].class, Object.class);
                Method exception = getMethod(cls, "exception", Object.class, Method.class, Object[].class, Throwable.class);

                Class<?>[] clazzes = aspect.value();
                for (Class<?> clazz : clazzes) {
                    addInterceptMethod(clazz, InterceptorPoint.BEFOR, before);
                    addInterceptMethod(clazz, InterceptorPoint.AFTER, after);
                    addInterceptMethod(clazz, InterceptorPoint.EXCEPTION, exception);
                }
            }
        }
    }

    private static void addInterceptMethod(Class<?> cls, InterceptorPoint point, Method method) {
        if (method == null) {
            return;
        }
        Map<InterceptorPoint, List<Method>> map = interceptMethodMap.get(cls);
        if (map == null) {
            map = new HashMap<>();
            interceptMethodMap.put(cls, map);
        }
        List<Method> methods = map.get(point);
        if (methods == null) {
            methods = new ArrayList<>();
            map.put(point, methods);
        }
        methods.add(method);
    }

    private static Method getMethod(Class<?> cls, String methodName, Class<?>... args) {
        try {
            return cls.getMethod(methodName, args);
        } catch (NoSuchMethodException e) {
        }
        return null;
    }

}
